/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 chain_lightning.h

	$Header: /heroes4/chain_lightning.h $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )
#include "chain_lightning.h"

#include "adaptor_handler.h"
#include "battlefield.h"
#include "can_cast.h"
#include "combat_ai.h"
#include "combat_creature.h"
#include "combat_spell_registration.h"
#include "function_handler.h"
#include "magic_mirror.h"
#include "missile_type.h"
#include "move_missile.h"
#include "random.h"

static t_combat_spell_registration<t_chain_lightning>
       k_lightning_register( k_spell_chain_lightning );

static const int k_max_targets = 5;

// Generate the list of possible targets after the current one
// target must already be in the already_hit list
static void get_next_target_list( t_battlefield&				battlefield,
								  t_combat_creature const*		caster, 
								  const t_combat_creature_ptr&	target,
								  const t_combat_creature_list&	already_hit,
								  t_combat_creature_list&		targets)
{
	t_combat_creature_list::iterator       index = battlefield.creatures_begin();
	t_combat_creature_list::iterator       end   = battlefield.creatures_end();
	t_combat_creature_list::const_iterator old_creature;
	t_map_point_2d					       center = target->get_footprint_center();
	t_map_point_2d					       delta;
	int								       distance;
	int								       best_distance;

	targets.clear();

	for (; index != end; index++)
	{
		old_creature = std::find( already_hit.begin(), already_hit.end(), *index );
		if (old_creature != already_hit.end())
			continue;

		if (!can_affect( *caster, k_spell_chain_lightning, **index ))
			continue;

		delta = index->get()->get_footprint_center() - center;
		distance = delta.column * delta.column + delta.row * delta.row;
		if (!targets.empty())
		{
			if (distance > best_distance)
				continue;
			if (distance < best_distance)
				targets.clear();
		}
		best_distance = distance;
		targets.push_back( *index );
	}
}

// ----------------------------------------------------------------
// class to find next chain lightning target
// ----------------------------------------------------------------
namespace
{
	class t_chain_bolt : public t_handler_base_1<t_window*>
	{
	public:
		t_chain_bolt( t_battlefield&					battlefield, 
					  t_combat_creature_ptr				caster, 
					  t_combat_creature_ptr				creature, 
			          int								power,
					  t_combat_action_message const &	action_message );

		virtual void operator()( t_window* );
		void         impact();
	protected:
		void get_next_target();

		t_combat_action_message m_action_message;
		t_combat_creature_list	m_already_hit;
		t_battlefield&			m_battlefield;
		t_combat_creature_ptr	m_caster;
		int						m_power;
	};
	typedef t_counted_ptr<t_chain_bolt> t_chain_bolt_ptr;
};


// ----------------------------------------------------------------
// class to find next chain lightning target
// ----------------------------------------------------------------
static void bolt_impact( t_combat_creature_ptr caster, t_map_point_2d, t_chain_bolt_ptr bolt )
{
	bolt->impact();
}

// ----------------------------------------------------------------
// class to find next chain lightning target
// ----------------------------------------------------------------
t_chain_bolt::t_chain_bolt( t_battlefield& battlefield, t_combat_creature_ptr caster,
						    t_combat_creature_ptr creature, int power, 
							t_combat_action_message const &	action_message )
			: m_battlefield( battlefield ), m_action_message( action_message )
{
	m_caster = caster;
	m_power = power;
	m_already_hit.push_back( creature );
}

// ----------------------------------------------------------------
// class to find next chain lightning target
// handle start of a new bolt
// ----------------------------------------------------------------
void t_chain_bolt::operator()( t_window* )
{
	get_next_target();
}

// ----------------------------------------------------------------
// class to find next chain lightning target
// handle start of a new bolt
// ----------------------------------------------------------------
void t_chain_bolt::get_next_target()
{
	t_combat_creature_ptr target = m_already_hit.back();

	target->apply_damage( m_battlefield, true );
	m_power = m_power / 2;
	if (m_power <= 0 || m_already_hit.size() >= k_max_targets)
	{
		m_battlefield.update_state();
		return;
	}

	t_combat_creature_list targets;
	get_next_target_list( m_battlefield, m_caster, target, m_already_hit, targets);

	if (targets.empty())
	{
		m_battlefield.update_state();
		return;
	}

	int count = random( targets.size() );

	
	t_combat_creature_list::iterator index;
	for (index = targets.begin(); index != targets.end(); index++)
	{
		if (count-- <= 0)
			break;
	}

	t_combat_creature* next_target = *index;
	t_missile_handler  handler;

	handler = add_3rd_argument( function_3_handler( bolt_impact ), t_chain_bolt_ptr(this) );
	m_already_hit.push_back( next_target );

	m_battlefield.launch_missile( k_missile_chain_lightning, *target, target->get_body_center(),
								  next_target->get_body_center(),
								  handler, m_action_message );
}

// ----------------------------------------------------------------
// class to find next chain lightning target
// handle impact of bolt on a secondary target
// ----------------------------------------------------------------
void t_chain_bolt::impact()
{
	t_combat_creature_ptr target	  = m_already_hit.back();
	int					  damage;

	damage = target->modify_spell_damage( m_caster, m_power, k_spell_lightning );
	target->add_damage( damage, m_caster, false, false, m_action_message );
	target->check_flinch( m_action_message );
	get_next_target();
}

// ----------------------------------------------------------------
// handler for chain lightning
// ----------------------------------------------------------------
t_chain_lightning::t_chain_lightning( t_battlefield& battlefield, t_spell spell )
                 : t_combat_spell_single_target( battlefield, spell )
{
}

// ----------------------------------------------------------------
// recursive functiuon to determine weight of chain lightning spell
// ----------------------------------------------------------------
static double ai_weight_recurse( t_battlefield&				battlefield,
								 t_combat_creature const&	caster,
								 t_combat_creature*			target, 
								 int						power, 
								 t_combat_creature_list		already_hit )
{
	double weight;

	int damage = target->modify_spell_damage( &caster, power, k_spell_lightning );

	if (damage > target->get_total_hits())
		damage = target->get_total_hits();

	// Weight of hurting this creature
	weight = target->get_ai_value( damage );

	weight += t_combat_ai::get_zoc_change_weight(damage, *target, false, 0);

	if (target->belongs_to_defender() == caster.get_controller() )
	{
		weight = -weight;
	}

	power = power / 2;

	if (power == 0 || already_hit.size() == k_max_targets - 1)
		return weight;

	already_hit.push_back( target );

	t_combat_creature_list targets;
	get_next_target_list( battlefield, &caster, target, already_hit, targets);

	t_combat_creature_list::iterator   index = targets.begin();
	t_combat_creature_list::iterator   end   = targets.end();

	while (index != end)
	{
		weight += ai_weight_recurse( battlefield, caster, *index, power, already_hit) / targets.size();
		index++;
	}

	return weight;
}

// ----------------------------------------------------------------
// determine weight of chain lightning spell
// ----------------------------------------------------------------
double t_chain_lightning::ai_weight( t_combat_creature const& target) const
{
	int power = m_caster->get_spell_power(m_spell);

	return ai_weight_recurse( m_battlefield, *m_caster, const_cast<t_combat_creature*>(&target), power, 
							  t_combat_creature_list() );
}

double t_chain_lightning::get_cancel_weight( t_combat_creature const& target ) const
{
	return 0.0;
}

// ----------------------------------------------------------------
// handler for chain lightning
// ----------------------------------------------------------------
bool t_chain_lightning::cast_on( t_combat_creature_ptr caster,
								 t_combat_creature_ptr target, 
								 int power,
								 bool is_mirror )
{
	t_window_handler		 handler;
	int						 damage;
	t_combat_actor_action_id action;
	t_combat_action_message  message( *caster, get_action_text( is_mirror ) );

	handler = new t_chain_bolt( m_battlefield, m_caster, target, power, message );
	damage = target->modify_spell_damage( caster, power, k_spell_lightning );
	target->add_damage( damage, caster, false, false, message );
	m_battlefield.start_animation( *target, k_spell_lightning, handler, message );
	if (damage >= target->get_total_hits())
		action = k_combat_actor_action_die;
	else
		action = k_combat_actor_action_flinch;
	target->set_animation( action, message );
	return true;
}

